package timer

import (
	"time"
)

// one dispatcher per goroutine (goroutine not safe)
type Dispatcher struct {
	ChanTimer chan Timer
	set map[Timer]struct{}
}

func NewDispatcher(capacity int) (result *Dispatcher) {
	result = new(Dispatcher)
	result.ChanTimer = make(chan Timer, capacity)
	result.set = make(map[Timer]struct{})

	return
}

func (dispatcher *Dispatcher) Close() {
	for timer, _ := range dispatcher.set {
		timer.stop()
	}

	dispatcher.set = nil
}

func (dispatcher *Dispatcher) AfterCall(duration time.Duration, cb func()) Timer {
	once := new(Once)
	once.cb = cb
	once.Timer = time.AfterFunc(duration, func() {
		dispatcher.ChanTimer <- once
	})
	dispatcher.set[once] = struct{}{}

	return once
}

func (dispatcher *Dispatcher) LoopCall(duration time.Duration, cb func()) Timer {
	loop := new(Loop)
	loop.Once = new(Once)
	loop.Once.cb = cb
	loop.interval = duration
	loop.Timer = time.AfterFunc(duration, func() {
		dispatcher.ChanTimer <- loop
	})
	dispatcher.set[loop] = struct{}{}

	return loop
}

func (dispatcher *Dispatcher) CronCall(expr *CronExpr, cb func()) Timer {
	cron := new(Cron)

	now := time.Now()
	nextTime := expr.Next(now)
	if !nextTime.IsZero() {
		cron.Once = new(Once)
		cron.Once.cb = cb
		cron.expr = expr
		cron.Timer = time.AfterFunc(nextTime.Sub(now), func() {
			dispatcher.ChanTimer <- cron
		})
		dispatcher.set[cron] = struct{}{}
	}

	return cron
}

func (dispatcher *Dispatcher) Invoking(timer Timer) {
	if _, exists := dispatcher.set[timer]; !exists {
		return
	}

	if !timer.callback() {
		delete(dispatcher.set, timer)
	}
}

func (dispatcher *Dispatcher) Stop(timer Timer) {
	if timer == nil {
		return
	}

	delete(dispatcher.set, timer)
	timer.stop()
}
